package org.nanhill.support.spring.v12;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.nanhill.commons.pattern.domain.EntityManager;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

/**
 * @link http://www.hibernate.org/328.html
 * 
 * @author alin [xalinx at gmail dot com]
 * 
 */
public abstract class SpringHibernateEntityManager<E, ID extends Serializable>
		extends HibernateDaoSupport implements EntityManager<E, ID> {

	protected Class<E> entityClass;

	protected String entityClassName;

	protected String countEntityQuery;

	public SpringHibernateEntityManager() {
		entityClass = (Class<E>) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[0];
		entityClassName = entityClass.getCanonicalName();
		countEntityQuery = "select count(*) from " + entityClassName;
	}

	protected void initQuery(Query q, Object... values) {
		for (int i = 0; i < values.length; i++) {
			q.setParameter(i, values[i]);
		}
	}

	/**
	 * Use this inside subclasses as a convenience method.
	 */
	@SuppressWarnings("unchecked")
	protected List<E> findByCriteria(final Criterion... criterion) {
		return (List<E>) this.getHibernateTemplate().execute(
				new HibernateCallback() {
					public Object doInHibernate(Session session)
							throws HibernateException {
						Criteria crit = session.createCriteria(entityClass);
						for (Criterion c : criterion) {
							crit.add(c);
						}
						return crit.list();
					}
				});
	}

	@SuppressWarnings("unchecked")
	protected E findOne(final String query, final Object... values) {
		return (E) getHibernateTemplate().execute(new HibernateCallback() {
			public Object doInHibernate(Session session)
					throws HibernateException {
				Query q = session.createQuery(query);
				initQuery(q, values);
				return q.uniqueResult();
			}
		});
	}

	@SuppressWarnings("unchecked")
	protected List<E> findPart(final String query, final int start,
			final int size, final Object... values) {
		return (List<E>) getHibernateTemplate().execute(
				new HibernateCallback() {
					public Object doInHibernate(Session session)
							throws HibernateException {
						Query q = session.createQuery(query);
						initQuery(q, values);
						q.setFirstResult(start);
						q.setMaxResults(size);
						return q.list();
					}
				});
	}

	@SuppressWarnings("unchecked")
	protected E findOneByNamedQuery(final String queryName,
			final Object... values) {
		return (E) getHibernateTemplate().execute(new HibernateCallback() {
			public Object doInHibernate(Session session)
					throws HibernateException {
				Query q = session.getNamedQuery(queryName);
				initQuery(q, values);
				return q.uniqueResult();
			}
		});
	}

	@SuppressWarnings("unchecked")
	protected List<E> findPartByNamedQuery(final String queryName,
			final int start, final int size, final Object... values) {
		return (List<E>) getHibernateTemplate().execute(
				new HibernateCallback() {
					public Object doInHibernate(Session session)
							throws HibernateException {
						Query q = session.getNamedQuery(queryName);
						initQuery(q, values);
						q.setFirstResult(start);
						q.setMaxResults(size);
						return q.list();
					}
				});
	}

	@SuppressWarnings("unchecked")
	protected List<E> findByNamedQuery(Class<E> clz, final String queryName,
			final Object... values) {
		return (List<E>) this.getHibernateTemplate().findByNamedQuery(
				queryName, values);
	}

	public int findCount() {
		return (Integer) getHibernateTemplate().execute(new HibernateCallback() {
			public Object doInHibernate(Session session)
					throws HibernateException {
				// Query query = session.createQuery(countEntityQuery);
				// return query.uniqueResult();
				return ((Long) session.createQuery(countEntityQuery).iterate()
						.next()).longValue();
			}
		});
	}

	@SuppressWarnings("unchecked")
	public List<E> findAll() {
		return (List<E>) getHibernateTemplate().execute(
				new HibernateCallback() {
					public Object doInHibernate(Session session)
							throws HibernateException {
						Criteria c = session.createCriteria(entityClass);
						return c.list();
					}
				});
	}

	@SuppressWarnings("unchecked")
	public List<E> findPart(final int start, final int size) {
		return (List<E>) getHibernateTemplate().execute(
				new HibernateCallback() {
					public Object doInHibernate(Session session)
							throws HibernateException {
						Criteria c = session.createCriteria(entityClass);
						c.setFirstResult(start);
						c.setMaxResults(size);
						return c.list();
					}
				});
	}

	@SuppressWarnings("unchecked")
	public E findById(ID id) {
		return (E) this.getHibernateTemplate().get(entityClass, id);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.nanhill.commons.pattern.domain.EntityManager#save(E)
	 */
	public void save(E entity) {
		currentSession().saveOrUpdate(entity);
	}

	public void delete(E entity) {
		currentSession().delete(entity);
	}

	protected Session currentSession() {
		return getSessionFactory().getCurrentSession();
	}
}
